package monitor

import (
	"fmt"
	"github.com/astaxie/beego/logs"
	"github.com/astaxie/beego/orm"
	"strings"
	"time"
	"path/filepath"
	"os"
	"cloud_worm/util/ufile"
	"cloud_worm/wormweb/controllers/ctrconfig"
	"cloud_worm/util/umodels/mysqldb"
	"cloud_worm/util/umodels/mysqldb/dbmanager"
	"cloud_worm/util/umodels/mysqldb/dbmonitor"
	"cloud_worm/wormweb/controllers/ctrutil"
	"cloud_worm/util/uhttp"
	"cloud_worm/util/proto/ujson"
	"cloud_worm/wormweb/controllers/ctrinner/taskqueue"
)

type FileListController struct {
	ctrconfig.LoginCtr
}

func (c *FileListController) ignoreStr2Slice(i string) []string {
	tmp := strings.Split(i, ",")

	is := make([]string, 0, len(tmp))
	for _, item := range tmp {
		if len(strings.TrimSpace(item)) <= 0 {
			continue
		}

		is = append(is, strings.TrimSpace(item))
	}

	return is
}

func (c *FileListController) ignoreSlice2Str(l []string) string {
	return strings.Join(l, ",")
}

func (c *FileListController) List()  {
	c.Manager()

	offset, limit := c.DealPage()
	status, err := c.GetInt("status", mysqldb.StatusAll)
	fileName := c.GetString("filename")

	w := &dbmonitor.FileList{}
	count, err := w.Count(status, fileName)
	if err != nil {
		logs.Warn(fmt.Sprintf("FileList count error: %s", err.Error()))
		data := map[string]interface{}{"total": 0, "data": []dbmonitor.FileList{}}
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeSystemErr, "count error.", data)
		c.ServeJSON()
		return
	}
	if count == 0 {
		data := map[string]interface{}{"total": count, "data": []dbmonitor.FileList{}}
		c.Data["json"] = uhttp.CreateOkResponse(data)
		c.ServeJSON()
		return
	}
	l, err := w.Select(offset, limit, status, fileName)
	if err != nil {
		logs.Warn(fmt.Sprintf("FileList select error: %s", err.Error()))
		data := map[string]interface{}{"total": 0, "data": []dbmonitor.FileList{}}
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeSystemErr, "select error.", data)
		c.ServeJSON()
		return
	}

	data := map[string]interface{}{"total": count, "data": l}
	c.Data["json"] = uhttp.CreateOkResponse(data)
	c.ServeJSON()
	return
}

func (c *FileListController) Create()  {
	c.Manager()

	type InputInfo struct {
		FileName 	string		`json:"file_name"`
		Ignore 		string		`json:"ignore"`
	}
	var input InputInfo
	if err := c.Unmarshal(&input); err != nil {
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeReqBodyErr, err.Error(), nil)
		c.ServeJSON()
		return
	}
	if input.FileName == mysqldb.SelectStrAll {
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeParamErr, "input params error.", nil)
		c.ServeJSON()
		return
	}

	w := &dbmonitor.FileList{FileName: input.FileName}
	if err := w.Read("file_name"); err == nil {
		logs.Error(fmt.Sprintf("had file name: %s", input.FileName))
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeParamErr, "file name is had.", nil)
		c.ServeJSON()
		return
	}
	w.IgnoreList = c.ignoreSlice2Str(c.ignoreStr2Slice(input.Ignore))
	w.Created = time.Now().Unix()
	w.Status = mysqldb.StatusValid
	w.Updated = time.Now().Unix()

	if err := w.Insert(); err != nil {
		logs.Error(fmt.Sprintf("FileList insert error: %s", err.Error()))
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeSystemErr, "insert error.", nil)
		c.ServeJSON()
		return
	}

	fd, err := os.OpenFile(fileRuleFilePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0600)
	if err != nil {
		logs.Error(fmt.Sprintf("FileList create file error: %s", err.Error()))
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeSystemErr, "create file error.", nil)
		c.ServeJSON()
		return
	}
	defer fd.Close()

	c.Data["json"] = uhttp.CreateOkResponse(nil)
	c.ServeJSON()
	return
}

func (c *FileListController) Edit() {
	c.Manager()
	
	type InputInfo struct {
		Id 			int64		`json:"id"`
		FileName 	string		`json:"file_name"`
		Ignore 		string		`json:"ignore"`
		Status 		int			`json:"status"`
	}
	var input InputInfo
	if err := c.Unmarshal(&input); err != nil {
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeReqBodyErr, err.Error(), nil)
		c.ServeJSON()
		return
	}
	if input.FileName == mysqldb.SelectStrAll || (input.Status != mysqldb.StatusInvalid && input.Status != mysqldb.StatusValid) {
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeParamErr, "input status error.", nil)
		c.ServeJSON()
		return
	}

	w := &dbmonitor.FileList{Id: input.Id}
	if err := w.Read("id"); err != nil {
		logs.Warn(fmt.Sprintf("FileList edit no record for id: %d", input.Id))
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeParamErr, "input id error", nil)
		c.ServeJSON()
		return
	}
	if input.FileName != w.FileName || input.Ignore != w.IgnoreList || input.Status != w.Status {
		w.FileName = input.FileName
		w.Status = input.Status
		w.IgnoreList = c.ignoreSlice2Str(c.ignoreStr2Slice(input.Ignore))
		w.Updated = time.Now().Unix()
		if err := w.Update("file_name", "status", "ignore_list", "updated"); err != nil {
			logs.Warn(fmt.Sprintf("FileList updated error: %s", err.Error()))
			c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeSystemErr, "updated error.", nil)
			c.ServeJSON()
			return
		}
	}

	fd, err := os.OpenFile(fileRuleFilePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0600)
	if err != nil {
		logs.Error(fmt.Sprintf("FileList create file error: %s", err.Error()))
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeSystemErr, "create file error.", nil)
		c.ServeJSON()
		return
	}
	defer fd.Close()

	c.Data["json"] = uhttp.CreateOkResponse(nil)
	c.ServeJSON()
	return
}

// FileListController.CommitFlag 标记是否需要commit处理
// file_list_key_file 文件存在表明是待commit
// 因为commit时，会将file_list_key_file rename为file_list_key_file.bak，所以一旦commit过，file_list_key_file文件就不会存在
func (c *FileListController) CommitFlag()  {
	c.Manager()

	commit := false
	_, err := os.Stat(fileRuleFilePath)
	if err != nil {
		commit = false
	} else {
		commit = true
	}

	c.Data["json"] = uhttp.CreateOkResponse(map[string]interface{}{"commit": commit})
	c.ServeJSON()
	return
}

func (c *FileListController) Commit()  {
	c.Manager()

	fileList := &dbmonitor.FileList{}
	fileLists, err := fileList.Select(mysqldb.SelectOffsetZero, mysqldb.SelectLimitAll, mysqldb.StatusValid, mysqldb.SelectStrAll)
	if err != nil {
		logs.Error(fmt.Sprintf("select all filelist error: %s", err.Error()))
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeSystemErr, "select all filelist key error", nil)
		c.ServeJSON()
		return
	}
	fd, err := os.OpenFile(fileRuleFilePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0600)
	if err != nil {
		logs.Error(fmt.Sprintf("open file'%s' error: %s", fileRuleFilePath, err.Error()))
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeSystemErr, fmt.Sprintf("open file'%s' error", fileRuleFilePath), nil)
		c.ServeJSON()
		return
	}
	for _, item := range fileLists {
		// 文件内容的格式为：filename|ignore1,ignore2,
		fd.WriteString(fmt.Sprintf("%s|%s\n", item.FileName, item.IgnoreList))
	}
	fd.Close()

	newMd5 := ufile.GetFileMd5(fileRuleFilePath)
	oldMd5 := ufile.GetFileMd5(fileRuleFileBakPath)
	if newMd5 == oldMd5 {
		os.Rename(fileRuleFilePath, fileRuleFileBakPath)				// 配置内容一样，则仅执行rename操作
		c.Data["json"] = uhttp.CreateOkResponse(nil)
		c.ServeJSON()
		return
	}

	downloadURL, err := ctrutil.ResourceSet().UploadFile(fileRuleFilePath, ctrutil.UploadWormURL)
	if err != nil {
		logs.Error(fmt.Sprintf("upload file to router error: %s", err.Error()))
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeSystemErr, "upload file to router error", nil)
		c.ServeJSON()
		return
	}
	os.Rename(fileRuleFilePath, fileRuleFileBakPath)

	// 如果没有启动文件监控的功能，则仅将配置文件提交
	if FileMonitor == false {
		logs.Debug("file monitor=%v, commit config file", FileMonitor)
		c.Data["json"] = uhttp.CreateOkResponse(nil)
		c.ServeJSON()
		return
	}

	wormId := dbmanager.WormId{}
	wormIds, err := wormId.SelectStatusVerOver(mysqldb.SelectOffsetZero, mysqldb.SelectLimitAll, dbmanager.AgentOnline,
		FileMonitorConfigVersion,  ctrutil.RunMode)
	if err != nil && err != orm.ErrNoRows {
		logs.Error(fmt.Sprintf("FileListController get online agent failed: %s", err.Error()))
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeSystemErr, "FileListController get online agent failed.", nil)
		c.ServeJSON()
		return
	}

	// 转发出去
	confPayLoad := ujson.ConfigFilePayLoad{
		FileURL: downloadURL,
		MD5: newMd5,
	}
	payLoad, err := confPayLoad.MarshalJSON()
	if err != nil {
		logs.Error(fmt.Sprintf("file ConfigFilePayLoad marshal json failed: %s", err.Error()))
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeSystemErr, "ConfigFilePayLoad marshal json failed.", nil)
		c.ServeJSON()
		return
	}
	for _, item := range wormIds {
		notifyItem := &NotifyAutoConfig{
			AccessSrv: item.ServerAddr,
			AgentID: item.AgentId,
			AgentIP: item.IP,
			NotifyType: ujson.NtFileRule,
			PayLoad: payLoad,
		}

		taskqueue.FastQueInst().MsgQ.Push(notifyItem)
	}

	c.Data["json"] = uhttp.CreateOkResponse(nil)
	c.ServeJSON()
	return
}

func (c *FileListController) EnableSet()  {
	c.Manager()

	type InputInfo struct {
		Enable 		bool		`json:"enable"`
	}
	var input InputInfo
	if err := c.Unmarshal(&input); err != nil {
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeReqBodyErr, err.Error(), nil)
		c.ServeJSON()
		return
	}

	if input.Enable == FileMonitor {
		logs.Debug("file monitor enable had: ", FileMonitor)
		c.Data["json"] = uhttp.CreateOkResponse(nil)
		c.ServeJSON()
		return
	}
	FileMonitor = input.Enable
	fileMonitorUpdate()

	// 选择目标
	wormId := dbmanager.WormId{}
	wormIds, err := wormId.SelectStatusVerOver(mysqldb.SelectOffsetZero, mysqldb.SelectLimitAll, dbmanager.AgentOnline,
		FileMonitorConfigVersion, ctrutil.RunMode)
	if err != nil && err != orm.ErrNoRows {
		logs.Error(fmt.Sprintf("file monitor enable get online agent failed: %s", err.Error()))
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeSystemErr, "file monitor enable get online agent failed.", nil)
		c.ServeJSON()
		return
	}

	// 转发出去
	enablePayLoad := ujson.EnablePayLoad{
		Enable: FileMonitor,
	}
	payLoad, err := enablePayLoad.MarshalJSON()
	if err != nil {
		logs.Error(fmt.Sprintf("file EnablePayLoad marshal json failed: %s", err.Error()))
		c.Data["json"] = uhttp.CreateErrResponse(uhttp.RespCodeSystemErr, "EnablePayLoad marshal json failed.", nil)
		c.ServeJSON()
		return
	}
	for _, item := range wormIds {
		notifyItem := &NotifyAutoConfig{
			AccessSrv: item.ServerAddr,
			AgentID: item.AgentId,
			AgentIP: item.IP,
			NotifyType: ujson.NtFileMonitor,
			PayLoad: payLoad,
		}

		taskqueue.FastQueInst().MsgQ.Push(notifyItem)
	}

	c.Data["json"] = uhttp.CreateOkResponse(nil)
	c.ServeJSON()
	return
}

func (c *FileListController) EnableGet()  {
	c.Manager()

	c.Data["json"] = uhttp.CreateOkResponse(map[string]interface{}{"enable": FileMonitor})
	c.ServeJSON()
	return
}

func fileMonitorUpdate() {
	conf := &dbmanager.WebConfig{Name: FileMonitorEnableName}
	if err := conf.Read("name"); err == nil {
		conf.Value = fmt.Sprintf("%v", FileMonitor)
		conf.Updated = time.Now().Unix()
		conf.Update("value", "updated")
	} else {
		logs.Error(fmt.Sprintf("no '%s' config record", FileMonitorEnableName))
	}
	return
}

var FileMonitor bool

const FileMonitorEnableName = "file_monitor_enable"

func FileMonitorLoad() error {
	conf := &dbmanager.WebConfig{Name: FileMonitorEnableName}
	if err := conf.Read("name"); err == nil {
		switch conf.Value {
		case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "Y", "y", "ON", "on", "On":
			FileMonitor = true
		case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "N", "n", "OFF", "off", "Off":
			FileMonitor = false
		default:
			return fmt.Errorf("config value format error")
		}
	} else {
		return fmt.Errorf("读WolConfig(%s)错误: %s", FileMonitorEnableName, err.Error())
	}

	logs.Info(fmt.Sprintf("set %s=%v", FileMonitorEnableName, FileMonitor))
	return nil
}

func init()  {
	ctrconfig.RegisterConfigLoad(FileMonitorEnableName, FileMonitorLoad)

	// 尝试创建该列数据
	conf := &dbmanager.WebConfig{Name: FileMonitorEnableName}
	if err := conf.Read("name"); err != nil {
		conf.Commit = "命令监控开关"
		conf.Type = dbmanager.BoolValue
		conf.Value = "false"
		conf.Created = time.Now().Unix()
		conf.Updated = conf.Created
		conf.Status = mysqldb.StatusValid
		conf.Insert()
	}

	FileMonitorLoad()
}

var fileRuleFilePath string
var fileRuleFileBakPath string
func init()  {
	fileRuleFilePath = filepath.Join(ctrutil.TmpDir, ujson.FileRuleFile)
	fileRuleFileBakPath = filepath.Join(ctrutil.TmpDir, ujson.FileRuleFile + ".bak")
}